#include <string>
#include <memory.h>
#include <thread>
#include <sstream>
#include <algorithm>

#include "defines.hpp"
#include "url.hpp"
#include "dwld.hpp"
#include <stdlib.h>
#include "misc.h"

using namespace std;

static int libcurl_download_dbg( CURL * p_ctx , curl_infotype itype, char * pData, size_t size, void *)
{
	if(itype == CURLINFO_TEXT){
		printf("[TEXT]\t%s\n", pData);
	}
	else if(itype == CURLINFO_HEADER_IN)  {
		printf("[HEADER_IN]\t%s\n", pData);
	}
	else if(itype == CURLINFO_HEADER_OUT){
		printf("[HEADER_OUT]\t%s\n", pData);
	}
	else if(itype == CURLINFO_DATA_IN)  {
		printf("[DATA_IN]\t%s\n", pData);
	}
	else if(itype == CURLINFO_DATA_OUT)  {
		printf("[DATA_OUT]\t%s\n", pData);
	}
	return 0;
}

size_t libcurl_download_on_recv( char * ptr , size_t size , size_t nmemb , void * obj )
{
	if( obj == nullptr ) return 0;
	if( ptr == nullptr ) return 0;

	Downloader   * p_obj = ( Downloader*)obj;

	p_obj->OnRecv( ptr , nmemb );

	return size * nmemb;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Downloader :: enable_dbg_msg()
{
	if( m_dbg == true ){
		curl_easy_setopt( p_ctx , CURLOPT_VERBOSE, 		 1		 );
		curl_easy_setopt( p_ctx , CURLOPT_DEBUGFUNCTION, libcurl_download_dbg );
	}else{
		curl_easy_setopt( p_ctx , CURLOPT_VERBOSE, 		 0 		 );
		curl_easy_setopt( p_ctx , CURLOPT_DEBUGFUNCTION, nullptr );		
	}
}

void Downloader :: enable_ssl()
{
	if( m_caPath.empty() )  {
		curl_easy_setopt( p_ctx , CURLOPT_SSL_VERIFYPEER, false );
		curl_easy_setopt( p_ctx , CURLOPT_SSL_VERIFYHOST, false );
	}else {
		curl_easy_setopt( p_ctx , CURLOPT_SSL_VERIFYPEER, true );
		curl_easy_setopt( p_ctx , CURLOPT_CAINFO		, m_caPath.c_str() );
	}
}

void Downloader :: general_opts()
{
	curl_easy_setopt( p_ctx , CURLOPT_READFUNCTION	, NULL );
	curl_easy_setopt( p_ctx , CURLOPT_WRITEFUNCTION	, libcurl_download_on_recv );
	curl_easy_setopt( p_ctx , CURLOPT_WRITEDATA		, this );
	curl_easy_setopt( p_ctx , CURLOPT_NOSIGNAL		, 1 );

	curl_easy_setopt( p_ctx , CURLOPT_CONNECTTIMEOUT, 3 );
	curl_easy_setopt( p_ctx , CURLOPT_TIMEOUT		, 3 );
}

Downloader :: err_code 
Downloader :: https_get( const std::string & strUrl , std::function< void ( err_code , const char * , size_t ) > fun )
{
	CURLcode res;
	if(NULL == p_ctx ) {
		return ERR_CURL_CTX_NULL;
	}
	
	if( fun ) m_cb = fun;

	enable_dbg_msg();
	curl_easy_setopt( p_ctx , CURLOPT_URL, strUrl.c_str());
	
	if( m_ssl == true ){
		curl_easy_setopt( p_ctx , CURLOPT_PROTOCOLS,  CURLPROTO_HTTPS );
		enable_ssl();
	}
	else
		curl_easy_setopt( p_ctx , CURLOPT_PROTOCOLS , CURLPROTO_HTTP );

	curl_easy_setopt( p_ctx , CURLOPT_HTTPGET , 1 );

	general_opts();

	res = curl_easy_perform( p_ctx );
	if( res != CURLE_OK ){
		return ERR_HTTP_PERFORM;
	}
	return OK;
}

Downloader :: err_code 
Downloader :: https_post( const string & strUrl, const string & data  )
{
	CURLcode res;
	if(NULL ==  p_ctx )	{
		return ERR_CURL_CTX_NULL;
	}

	if( m_ssl == true ){
		curl_easy_setopt( p_ctx , CURLOPT_PROTOCOLS, CURLPROTO_HTTPS );
		enable_ssl();
	}else{
		curl_easy_setopt( p_ctx , CURLOPT_PROTOCOLS, CURLPROTO_HTTP );
	}
	curl_easy_setopt( p_ctx , CURLOPT_URL		, strUrl.c_str()	);
	curl_easy_setopt( p_ctx , CURLOPT_POST		, 1					);
	curl_easy_setopt( p_ctx , CURLOPT_POSTFIELDS, data.c_str()	);
	
	general_opts();

	res = curl_easy_perform( p_ctx );
	if( res != CURLE_OK ){
		return ERR_HTTP_PERFORM;
	}
	return OK;
}

Downloader :: err_code 
Downloader :: https_post( const std::string& url , const ArrayString& headers , const std::string& data , const std::string& file )
{
	err_code ret = OK;

	return ret;
}

Downloader :: err_code 
Downloader :: https_post( const std::string & strUrl, const ArrayString& heads, const std::string & data , std::function< void ( err_code , const char * , size_t ) > fun )
{
	CURLcode res;
	if(NULL ==  p_ctx )	{
		return ERR_CURL_CTX_NULL;
	}

	if( fun ) m_cb = fun;

	struct curl_slist *list = NULL;

	for( size_t i = 0; i < heads.size(); i ++ ){
		list = curl_slist_append( list , heads[ i ].c_str() );
	}
	curl_easy_setopt(  p_ctx  , CURLOPT_HTTPHEADER , list );
	if( m_ssl == true ){
		curl_easy_setopt( p_ctx , CURLOPT_PROTOCOLS, CURLPROTO_HTTPS );
		enable_ssl();
	}else{
		curl_easy_setopt( p_ctx , CURLOPT_PROTOCOLS, CURLPROTO_HTTP );
	}
	curl_easy_setopt( p_ctx , CURLOPT_URL, strUrl.c_str());
	curl_easy_setopt( p_ctx , CURLOPT_POST, 1);
	curl_easy_setopt( p_ctx , CURLOPT_POSTFIELDS, data.c_str());
	
	general_opts();

	res = curl_easy_perform( p_ctx );
	curl_slist_free_all( list );
	
	m_file.close();
	if( res != CURLE_OK ){
		return ERR_HTTP_PERFORM;
	}
	return OK;
}
//
Downloader :: err_code 
Downloader :: https_get( const string & strUrl, const ArrayString& heads , std::function< void ( err_code , const char * , size_t ) > fun )
{
	CURLcode res;

	if(NULL ==  p_ctx )    {
		return ERR_CURL_CTX_NULL;
	}
	
	if( fun ) m_cb = fun;

	enable_ssl();
	struct curl_slist *list = NULL;

	for( size_t i = 0; i < heads.size(); i ++ ){
		list = curl_slist_append( list , heads[ i ].c_str() );
	}
	curl_easy_setopt(  p_ctx  , CURLOPT_HTTPHEADER , list );

	curl_easy_setopt( p_ctx , CURLOPT_URL, strUrl.c_str());
	if( m_ssl == true ){
		curl_easy_setopt( p_ctx , CURLOPT_PROTOCOLS , CURLPROTO_HTTPS );
		enable_ssl();
	}else{
		curl_easy_setopt( p_ctx , CURLOPT_PROTOCOLS , CURLPROTO_HTTP );
	}
	general_opts();

	res = curl_easy_perform( p_ctx );
	curl_slist_free_all( list );

	m_file.close();
	if( res != CURLE_OK ){
		return ERR_HTTP_PERFORM;
	}
	return OK;
}

const std::string Downloader :: prepare_protocol( const URL& url )
{
	std::string protocol = url.Protocol();
	std::transform( protocol.begin() , protocol.end() , protocol.begin() , ::tolower );
	switch( hash_(protocol.c_str() ) ){
		case hash_compile_time( "https" ):
			m_ssl = true;
			m_dbg = false;
		break;
		case hash_compile_time( "http" ):
			m_ssl = false;
			m_dbg = false;
		break;
		case hash_compile_time( "ftp" ):
			m_ssl = false;
			m_dbg = false;		
		break;
		case hash_compile_time( "ftps" ):
			m_ssl = true;
			m_dbg = false;
		break;
		default:
			m_ssl = false;
			m_dbg = false;
	}

	return protocol;
}

//
Downloader :: err_code 
Downloader :: download_file( const string & strUrl, const string & strFile )
{
	URL  url( strUrl );

	std::string p = prepare_protocol( url );
	m_to = strFile;
	m_from = strUrl;

	prepare_file( m_to );

	switch( hash_( p.c_str() )){
		case hash_compile_time( "https" ):
		case hash_compile_time( "http" ):
			https_get( m_from , [this]( err_code , const char * data , size_t len ){

			});
		break;
		case hash_compile_time( "ftp" ):
		case hash_compile_time( "ftps" ):
			ftp( url ,  [this]( err_code , const char * data , size_t len ){

			});
		break;
		default:
			if( cb_finish){
				cb_finish( ERR_UNSUPPORTED_PROTOCOL );
			}
			return ERR_UNSUPPORTED_PROTOCOL;
	}
	return OK;
}

Downloader :: err_code
Downloader :: ftp( const URL& url , const std::string& tofile )
{
	if( nullptr == p_ctx ){
		return ERR_CURL_CTX_NULL;
	}
	std::string str;
	int port;
	std::stringstream ss;

	str = url.Protocol() + "://" + url.HostName() + "/" + url.Path();
	curl_easy_setopt( p_ctx , CURLOPT_URL 		, str.c_str() 	);
	curl_easy_setopt( p_ctx , CURLOPT_PROTOCOLS , CURLPROTO_FTP );

	str = url.HostPort();
	ss << str;
	ss >> port;
	curl_easy_setopt( p_ctx , CURLOPT_PORT 		, port 			);

	str = url.AuthUser() + ":" + url.AuthPswd();
	curl_easy_setopt( p_ctx , CURLOPT_USERPWD 	, str.c_str() 	);

	general_opts();
	CURLcode res = curl_easy_perform( p_ctx );
	if( res != CURLE_OK ){
		return ERR_FTP_PERFORM;
	}
	return OK;
}

Downloader :: err_code 
Downloader :: ftp( const URL& url , std::function< void ( err_code , const char * , size_t ) > fun )
{
	if( nullptr == p_ctx ){
		return ERR_CURL_CTX_NULL;
	}
	err_code ret = OK;

	m_cb = fun;
	std::string str;
	int port;
	std::stringstream ss;

	str = url.Protocol() + "://" + url.HostName() + "/" + url.Path();
	curl_easy_setopt( p_ctx , CURLOPT_URL 		, str.c_str() 	);
	curl_easy_setopt( p_ctx , CURLOPT_PROTOCOLS , CURLPROTO_FTP );

	str = url.HostPort();
	ss << str;
	ss >> port;
	curl_easy_setopt( p_ctx , CURLOPT_PORT 		, port 			);

	str = url.AuthUser() + ":" + url.AuthPswd();
	curl_easy_setopt( p_ctx , CURLOPT_USERPWD 	, str.c_str() 	);

	general_opts();
	CURLcode res = curl_easy_perform( p_ctx );
	if( res != CURLE_OK ){
		return ERR_FTP_PERFORM;
	}

	return ret;
}

void Downloader :: OnRecv( const char * data , size_t len )
{
	try{
		m_file.write( data , len );
		
		if( m_cb ){
			m_cb( OK , data , len );
		}
	}catch( std::exception& e ){
		__show_line_1( "%s%s%s" , TRED , e.what() , TNORMAL );
		if( m_cb )
			m_cb( ERR_SYS_WRITE_FILE , data , len );
	}
}

void Downloader :: Run( )
{
	if( p_ctx == nullptr ){
		throw ERR_CURL_CTX_NULL;
	}
	std::thread thd( [ this ]{
		download_file( m_from , m_to );
		if( cb_finish ){
			cb_finish( OK );
		}
	});

	thd.detach();
}

void Downloader :: Run( const std::string& url , const std::string& to )
{
	if( p_ctx == nullptr ){
		throw ERR_CURL_CTX_NULL;
	}
	if( url.empty() ){
		throw ERR_FROM_FILE_EMPTY;
	}
	if( to.empty() ){
		throw ERR_TO_FILE_EMPTY;
	}

	m_from = url;
	m_to = to;

	std::thread thd( [ this ]{
		download_file( m_from , m_to );
		if( cb_finish ){
			cb_finish( OK );
		}
	});

	thd.detach();
}


void Downloader :: onFinish( std::function< void (err_code)> fun )
{
	cb_finish = fun;
}

bool Downloader :: prepare_file( const std::string& file )
{
	bool ret = false;
	try{
		m_file.open( file , std::ios_base::out | std::ios_base::binary );

		if( m_file.is_open() == true ){
			ret = true;
		}
	}catch( std::exception& e ){
		ret = false;
	}

	return ret;
}

Downloader :: Downloader( )
{
	m_dbg = false;
	m_ssl = false;

	p_ctx = curl_easy_init();

	if( p_ctx == nullptr ){
		throw ERR_INIT_LIBCURL;
	}
}

Downloader :: Downloader( bool ssl , bool dbg )
{
	m_dbg = dbg;
	m_ssl = ssl;

	p_ctx = curl_easy_init();

	if( p_ctx == nullptr ){
		throw ERR_INIT_LIBCURL;
	}
}

Downloader :: Downloader( const std::string& url , const std::string & to , bool ssl , bool dbg )
{
	m_from = url;
	m_to = to;

	p_ctx = curl_easy_init();

	if( p_ctx == nullptr ){
		throw ERR_INIT_LIBCURL;
	}
}

Downloader :: ~Downloader()
{
	if( p_ctx ){
		curl_easy_cleanup( p_ctx );
	}
}